package nf;

import java.util.Map;
import java.util.List;
import nf.graph.Delta;
import nf.graph.Graph;
import nf.graph.Path;
import nf.graph.Edge;
import nf.graph.Node;
import nf.traversal.Traverser;
import nf.traversal.TraverserFactory;
import nf.utility.UtilityCalculator;
import nf.util.Rational;

/**
 * A network formation game.
 */
public class FormationGame extends EdgeGame
{	
	/**
	 * Creates a new network formation game.
	 * @param traverserFactory node traverser factory
	 * @param utilCalc utility calculator
	 */
	public FormationGame(TraverserFactory traverserFactory,
		UtilityCalculator utilCalc)
	{
		super(traverserFactory, utilCalc);
	}
	
	public Delta findBestResponse(Graph network, List<Node> nodes)
	{	
		switch (nodes.size()) {
			case 1:
				return bestIndividualResponse(network, nodes.get(0));
			case 2:
				return bestPairResponse(network, nodes.get(0), nodes.get(1));
			default:
				throw new IllegalArgumentException("Can only find the best " +
					"response for a single node or for a pair of nodes.");
		}
	}
	
	private Rational[] getUtilities(Graph network, Node... nodes)
	{
		Rational[] utils = new Rational[nodes.length];
		
		for (int i = 0; i < nodes.length; i++) {
			utils[i] = utilCalc.getUtility(network, nodes[i]);
		}
		
		return utils;
	}
	
	private Delta bestIndividualResponse(Graph network, Node node)
	{
		throw new UnsupportedOperationException("individual best responses");
	}
	
	private Delta bestPairResponse(Graph network, Node a, Node b)
	{
		Delta changes = new Delta();
		Rational[] utils = getUtilities(network, a, b);
		Rational[] newUtils;
		boolean worthwhile = false;
		
		if (network.connected(a, b)) {
			network.disconnect(a, b);
			
			try {
				newUtils = getUtilities(network, a, b);
				
				worthwhile = (newUtils[0].moreThan(utils[0]) ||
					newUtils[1].moreThan(utils[1]));
				if (worthwhile)
					changes.subtract(a, b);
			} finally {
				// Ensure that we always reconnect the edge if it was not
				// worthwhile, even if an exception is thrown.
				if (!worthwhile)
					network.connect(a, b);
			}
		} else {
			network.connect(a, b);
			try {
				newUtils = getUtilities(network, a, b);
				
				worthwhile = (newUtils[0].moreThan(utils[0]) &&
					newUtils[1].moreThan(utils[1]));
				if (worthwhile)
					changes.add(a, b);
			} finally {
				// Ensure that we always disconnect the edge if it was not
				// worthwhile to add it, even if an exception is thrown.
				if (!worthwhile)
					network.disconnect(a, b);
			}
		}
		
		return changes;
	}
}
